How to Persist React Components with Children after Page Reload Events? - javascript

This is more of a hypothetical question as I would like to have a clear idea before trying to write the code for this problem.
As an example, lets say I have Board (parent) and Card (children) components.
The Cards are placed inside the Board and contain some text elements inside (maybe Todo items) and can be moved around inside the board and from board to board. Thus, state is used to allow users to move the cards from board to board, then "check-off" a todo item, and not reset everything to original positions.
But what happens if I want to persist after a user reloads the page?
Is my only option to store everything in localStorage after "stringifying", or are there other (better) alternatives?
I see a lot of examples online with a single component where you simply store the state and text of that component, but that seems very inefficient and complex when it comes to components with children.

The usual way is using localStorage. A full example of how localStorage works, you can change the states with your own.
const LOCAL_STORAGE_KEY = 'todosvar'
useEffect(() => {
const storedTodos = JSON.parse(localStorage.getItem
(LOCAL_STORAGE_KEY))
if (storedTodos) setTodos(storedTodos)
}, [])
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(todosvar))
}, [todosvar])

Related

Call a function on a react child functional component from parent

I have a very large and complex React application. It is designed to behave like a desktop application. The interface is a document style interface with tabs, each tab can be one of many different type of editor component (there are currently 14 different editor screens). It is possible to have a very large number of tabs open at once (20-30 tabs). The application was originally written all with React class components, but with newer components (and where significant refactors have been required) I've moved to functional components using hooks. I prefer the concise syntax of functions and that seems to be the recommended direction to take in general, but I've encountered a pattern from the classes that I don't know how to replicate with functions.
Basically, each screen (tab) on the app is an editor of some sort (think Microsoft office, but where you can have a spreadsheet, text document, vector image, Visio diagram, etc all in tabs within the same application... Because each screen is so distinct they manage their own internal state. I don't think Redux or anything like that is a good solution here because the amount of individually owned bits of state are so complex. Each screen needs to be able to save it's current working document to the database, and typically provides a save option. Following standard object oriented design the 'save' function is implemented as a method on the top level component for each editor. However I need to perform a 'save-all' function where I iterate through all of the open tabs and call the save method (using a reference) on each of the tabs. Something like:
openTabs.forEach((tabRef) => tabRef.current.save());
So, If I make this a functional component then I have my save method as a function assigned to a constant inside the function:
const save = () => {...}
But how can I call that from a parent? I think the save for each component should live within that component, not at a higher level. Aside from the fact that would make it very difficult to find and maintain, it also would break my modular loading which only loads the component when needed as the save would have to be at a level above the code-splitting.
The only solution to this problem that I can think of is to have a save prop on the component and a useEffect() to call the save when that save prop is changed - then I'd just need to write a dummy value of anything to that save prop to trigger a save... This seems like a very counter-intuitive and overly complex way to do it.... Or do I simply continue to stick with classes for these components?
Thankyou,
Troy
But how can I call that from a parent? I think the save for each component should live within that component, not at a higher level.
You should ask yourself if the component should be smart vs dumb (https://www.digitalocean.com/community/tutorials/react-smart-dumb-components).
Consider the following:
const Page1 = ({ onSave }) => (...);
const Page2 = ({ onSave }) => (...);
const App = () => {
const handleSavePage1 = (...) => { ... };
const handleSavePage2 = (...) => { ... };
const handleSaveAll = (...) => {
handleSavePage1();
handleSavePage2();
};
return (
<Page1 onSave={handleSavePage1} />
<Page2 onSave={handleSavePage2} />
<Button onClick={handleSaveAll}>Save all</button>
);
};
You've then separated the layout from the functionality, and can compose the application as needed.
I don't think Redux or anything like that is a good solution here because the amount of individually owned bits of state are so complex.
I don't know if for some reason Redux is totally out of the picture or not, but I think it's one of the best options in a project like this.
Where you have a separated reducer for each module, managing the module's state, also each reducer having a "saveTabX" action, all of them available to be dispatched in the Root component.

How to control state of multiple inputs in react

I'm creating an app wherein a user can create a set of flashcards with a term and description each. I also want the flashcards to be editable as soon as they add a flashcard. So each flashcard they add gets pushed to a variable with a controlled state. Then I created a component that returns inputs with all of the flashcard data.
this is what it would like look like:
There's no problem with that it works fine. But I want them to be editable once added. So I added an onchange to each of the inputs and did this:
function handleChange(event){
const {value, name, dataset} = event.target
const index = dataset.index
const newFlashcardSet = [...flashcardValues]
newFlashcardSet[index][name] = value;
setFlashcardValues(newFlashcardSet)
}
so when the user tries to type on the submitted flashcard they can edit it.
PROBLEM
As you can probably tell, each key keystroke calls the function, and rerenders all the data. Causing it to eat up a lot of cpu usage, even though it is not noticeable at first, but as soon as you add more flashcards, you would definetly notice the slow performance, because it rerenders all the data every keystroke. What is the best way to go around this?

react-window and detecting changes in size of list items

So I have been trying to figure out a solution for big lists in react. and I almost have the perfect solution - but I can't seem to figure out how to get this one detail to work.
I have a list of cards that I want to render quickly - I'm using the react-window package
import{VariableSizeList as List} from 'react-window';
I have a component called MaterialCard, and a component that contains lists of MaterialCards:
On the MaterialCard if you click on an inspect button - it opens up the innards of the card revealing a form input section.
const [cardOpen, setCardOpen] = useState(false);
const cardInnards = //a bunch of jsx.
//in the return
<button className="btn-inspect" onClick={()=>setCardOpen(!cardOpen)}>inspect/edit</button>
{cardOpen?cardInnards:<div></div>}
In the multi-list component container - I have this code.
const materialCardsNew = materialsNew.map((x,i)=><MaterialCard props={x} key ={`${i}matsnew`} />);
//react-window function to make a row:
const Row = array=> ({ index }) => array[index] //is MaterialCard
//in the return
<List
height={755}
itemCount={materialCardsNew.length-1}
itemSize={()=>130} // this is where I'm having trouble.
width={634}
>
{Row(materialCardsNew)}
</List>
Currently the itemSize is what I'm trying to fix...
What happens currently is the item has a fixed size area it appears in - and (thanks to z-index) the innards appear over other items in the list.
What I want to happen is the item size of the MaterialCard that is open - to be of a larger size: such that it doesn't cover other cards - I want it to expand and I don't want it to cover other cards at all.
My trouble is I don't know how to read the component's internal state from the top - how do I determine in the list container component which card is open. I understand I require a function for this... but: well...
the pseudocode I've come up with is this: this of course does not work - but it is more or less what I want to do.
//resize function
const getHeight = (arr)=>(index)=>arr[index].state.cardOpen?500:100; //bigger size if the card is open
//jsx
itemSize={getHeight(materialCardsNew)}
//I've also tried arr[index].style.height,
//and searched through the arr[index] properties in the console to see
//if there was anything I could use... no luck.
My first ideas are bunk... and I'm not really sure how to approach this... I'm pretty sure I shouldn't need a massive additional array for each array of material cards (there are a few categories) to keep track of which cards are open... but I can't seem to find the correct way of going about this.
How do I accomplish this?
For this issue:
My trouble is I don't know how to read the component's internal state
from the top
Lift state up. So in the containing component, you can use a hook like in the top level component:
const [activeCard, setActiveCard] = useState()
And in the card, pass in the function:
<Card setActiveCard={setActiveCard} key={someKey} {...otherProps}/>
And, in the implementation of the card, you can have something like:
useEffect(() => setActiveCard(key), [key])
And the top level component will have the 'active' card information.
Not sure I completely was clear on the issue, but that is one mechanism for sending child information to the parent.
And, if I am understanding the issue, you could have some logic in the child component to check if the active card is equal to the card:
<Card setActiveCard={setActiveCard} activeCard={activeCard} key={someKey} {...otherProps} />
useEffect(() => activeCard === key ? setComponentSize(activeSize) : setComponentSize(defaultSize), [{dependency array}])
Of course, the setComponentSize would be in the top level component, and passed in a similar fashion to setting the card index in the top level. And if everything is set in the containing (parent component), you could just check the index vs the activeCard.
Finally, just make sure however you are checking for the active card, you cleanup and call setActiveCard(-1), or whatever the default parameter you might want to use when the active card's state changes.

React Beautiful DND & Redux: Using a single Redux store, but still reorder cards between lists?

I'm building what is essentially a Trello clone using React and Redux. Thanks to #Sagiv b.g, I've got logic in place to allow users to create multiple <Board/>, <List/> and <Card/> instances using a single Redux store.
But that logic breaks down when I want to move <Card/> components between different <List/>.
Example
If I create two lists, one called "To Do" and one called "Done", my Redux logic matches the separate React <List/> component instances to the correct <Board/> instance using my boards() reducer like so:
case ADD_LIST:
return state.map(board => {
if (board.id !== action.boardId) return board;
return {
...board,
lists: lists(board.lists, action)
};
});
If I then create a card in my "To Do" list called "Finish Kanban project", the logic is basically the same in Redux for matching the correct <Card/> instance to the correct <List/> instance, except it happens in the lists() reducer:
case ADD_CARD:
return state.map(list => {
if (list.listId !== action.listId) return list;
return {
...list,
cards: cards(list.cards, action)
};
});
So far so good! But if I want to move the card I just created from the "To Do" list to the "Done" list, I can't quite figure out the logic with React Beautiful DND and Redux combined.
React Beautiful DND wants to reorder the entire array, which means I need to update my array of cards in Redux accordingly. So I'm stuck on how to take a card out of one list and move it to another list using my current Redux logic.
Thanks for any direction. I'm ready to abandon Redux or React Beautiful DND altogether, but wanted to see if anyone has any ideas on how to make it work.
I'm currently working through the same issue. If you watch the free egghead videos for React Beautiful DnD: https://egghead.io/courses/beautiful-and-accessible-drag-and-drop-with-react-beautiful-dnd you will see that regardless of whether you are using redux, you need to write logic in your onDragEnd handler to update the state. In the video, he is using the useState() hook to manage the state of his lists. In your case, you would emulate what he does but do it in redux by dispatching an action. You can use the same state shape and basically the same code as what is shown in the video. You just need to do it in redux.

How much of this business logic belongs in Vuex?

I have a simple app which pulls products from an API and displays them on-page, like this:
I've added Vuex to the app so that the search results as well as the product search array doesn't disappear when the router moves the user to a specific product page.
The search itself consists of the following steps:
show loading spinner (update the store object)
dispatch an action to access the API
update the store object with products, spinner
decide if the product list is exhausted
hide loading spinner
You get the idea.
With all of the variables stored in Vuex, it stands to reason all of the business logic should belong there as well, but should it really?
I'm talking specifically about accessing store params such as productsExhausted (when there are no more products to display) or productPage (which increments every time the infinite scroller module is triggered) etc.
How much logic - and what kind - belongs in Vuex? How much does not?
I was under the impression that Vuex is used for storage only but since all of the data is located there, fetching it all back to the Vue app only to send it all back seems like an overly verbose way to address the problem.
Vuex allows you to share data !
For everything that concerns the state of the app its pretty straightforward.
All the data that can be used by multiple components should be added
to the store.
Now concerning the business logic, even though I find its not really clear in the official documentation, it should follow the same principle.
What I mean is that logic that can be used by multiple components should be stored in actions.
Moreover actions allows you to deal with async operations. Knowing this, your code that pulls the data should definitely be stored in vuex's actions.
What I think you should do is to put the request inside an action, then mutate the state of your variables and automatically your UI will reflect the changes.
Moreover, a good pattern to apply is to convert most of the logic to a state logic. For instance consider this demo of a jumping snowman. In here the click action results on updating a value from the store. Although the interesting part is that one component uses the watch functionnality to be notified when the store changes. This way we keep the logic inside the component but use the store as an event emitter.
var store = new Vuex.Store({
state: {
isJumping: 0
},
mutations: {
jump: function(state){
state.isJumping++;
}
}
})
Vue.component('snowman', {
template: '<div id="snowman" :class="color">⛄</div>',
computed: {
isJumping: function(){
return this.$store.state.isJumping;
}
},
watch: {
isJumping: function(){
var tl = new TimelineMax();
tl.set(this.$el,{'top':'100px'})
tl.to(this.$el, 0.2, {'top':'50px'});
tl.to(this.$el, 0.5, {'top':'100px', ease: Bounce.easeOut});
}
}
})

Categories