use context value as initial state value - react hooks - reactjs

Can We use context values to initiate a state variable inside a function component?
Here I am trying to initiate a component state with values from context. But the state doesnot update when context value changes.
function Parent() {
return (
<ContextProvider>
<Child />
</ContextProvider>
);
}
function Child() {
const mycontext = useContext(Context);
const [items, setItems] = useState(mycontext.users);
console.log(mycontext.users, items); //after clicking fetch, => [Object, Object,...], [] both are not equal. why??????
return (
<>
<button onClick={() => mycontext.fetch()}>fetch</button>
{/* <button onClick={()=>mycontext.clear()} >Clear</button> */}
{items.map(i => (
<p key={i.id}>{i.name}</p>
))}
</>
);
}
/* context.js */
const Context = React.createContext();
function ContextProvider({ children }) {
const [users, setUsers] = useState([]);
function fetchUsers() {
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => response.json())
.then(json => setUsers(json));
}
return (
<Context.Provider
value={{ users, fetch: fetchUsers, clear: () => setUsers([]) }}
>
{children}
</Context.Provider>
);
}
The above code can be tested in codesandbox.
I can use context values directly, but I want to maintain state inside the component.
If we cannot initiate state value with a context value, what is the best approach If I want to get data from context and also want to maintain state internally?

The argument to useState is only used once.
You do not need to copy context value in state and can directly use it from context.
If however you would like to do it you need to make use of useEffect
const [items, setItems] = useState(mycontext.users);
useEffect(() => {
setItems(mycontext.users);
}, [mycontext.users]);
updated demo

Related

How can I return my todos after I remove some through splice in this particular code

I was watching a tutorial on how to make todos, though my main focus was local storage use.
But when he made the delete button then I was a bit confused, the code below shows how he did it but I am not getting it.
Can anyone explain that I tried using the splice method to remove items from the array but I am not able to remove the items from the page?
Can you also suggest what should I do after using splice to return the array on the page?
Below is the code,
import "./styles.css";
import { useState, useEffect } from 'react'
import Todoform from './TodoForm'
export default function App() {
const [list, setlist] = useState("");
const [items, setitems] = useState([])
const itemevent = (e) => {
setlist(e.target.value);
}
const listofitem = () => {
setitems((e) => {
return [...e , list];
})
}
const deleteItems = (e) => {
// TODO: items.splice(e-1, 1);
// Is there any other way I can do the below thing .i.e
// to remove todos from page.
// this is from tutorial
setitems((e1)=>{
return e1.filter((er , index)=>{
return index!=e-1;
})
})
}
return (
<>
<div className='display_info'>
<h1>TODO LIST</h1>
<br />
<input onChange={itemevent} value={list} type="text" name="" id="" />
<br />
<button onClick={listofitem} >Add </button>
<ul>
{
items.map((e, index) => {
index++;
return (
<>
<Todoform onSelect={deleteItems} id={index} key={index} index={index} text={e} />
</>
)
})
}
</ul>
</div>
</>
)
}
And this is the TodoForm in this code above,
import React from 'react'
export default function Todoform(props) {
const { text, index } = props;
return (
<>
<div key={index} >
{index}. {text}
<button onClick={() => {
props.onSelect(index)
}} className="delete">remove</button>
</div>
</>
)
}
Here is the codeSandbox link
https://codesandbox.io/s/old-wood-cbnq86?file=/src/TodoForm.jsx:0-317
I think one issue with your code example is that you don't delete the todo entry from localStorage but only from the components state.
You might wanna keep localStorage in sync with the components state by using Reacts useEffect hook (React Docs) and use Array.splice in order to remove certain array elements by their index (Array.splice docs).
// ..
export default function App() {
const [list, setlist] = useState("");
const [items, setitems] = useState([])
/* As this `useEffect` has an empty dependency array (the 2nd parameter), it gets called only once (after first render).
It initially retrieves the data from localStorage and pushes it to the `todos` state. */
useEffect(() => {
const todos = JSON.parse(localStorage.getItem("notes"));
setitems(todos);
}, [])
/* This `useEffect` depends on the `items` state. That means whenever `items` change, this hook gets re-run.
In here, we set sync localStorage to the current `notes` state. */
useEffect(() => {
localStorage.setItem("notes", JSON.stringify(items));
}, [items])
const itemevent = (e) => {
setlist(e.target.value);
}
const listofitem = () => {
setitems((e) => {
return [...e , list];
})
}
const deleteItems = (index) => {
// This removes one (2nd parameter) element(s) from array `items` on index `index`
const newItems = items.splice(index, 1)
setitems(newItems)
}
return (
<>
{/* ... */}
</>
)
}
There are multiple ways to remove an item from a list in JS, your version of splicing the last index is correct too and it is able to remove the last item. What it can't do is update your state.
His code is doing two things at the same time: Removing the last item of the Todo array and then, setting the resulted array in the state which updates the todo list.
So, change your code as
const deleteItems = (e) => {
let newItems = [...items];
newItems.splice(e-1, 1);
setitems(newItems);
}

How to use useEffect? so it wont duplicates displayed products using filter function? Reactjs-redux

My data from menProducts coming from store component just loops too many. Meaning it duplicates or renders too many when using my filter function. I've read using useEffect can render it only once but I don't know how to trigger its effect.
const [filter, setFilter] = useState('');
const menProducts = useSelector((state) => state.menProducts);
const SearchText = (event) => { <--- this function is for input search bar
setFilter(event.target.value);
}
useEffect(() => { <--- ??
}, []);
let dataSearch = menProducts.filter(id => { <-----Filter function
return Object.keys(id).some(key=>
id[key].toString().toLowerCase().includes(filter.toString().toLowerCase())
)
return (
<main>
{dataSearch.map((menproduct) => (
<ProductMen key={menproduct}/> <---imported <ProductMen/> component is a component that use useDispatch for store reducer and it also displayed products.
))}
</main>
)
}
You don't need to useEffect in this case, you just have to apply the filter at the right time like this:
const [filter, setFilter] = useState("");
const menProducts = useSelector((state) => state.menProducts);
const SearchText = (event) => {
setFilter(event.target.value);
};
return (
<main>
{menProducts
.filter((menproduct) =>
Object.keys(menproduct).some((key) =>
menproduct[key]
.toString()
.toLowerCase()
.includes(filter.toString().toLowerCase())
)
)
.map((menproduct) => (
<ProductMen key={menproduct} />
))}
</main>
);
Demo: https://stackblitz.com/edit/react-6lfqps?file=src/App.js
In the demo i've also included an alternative that use useEffect if you want to take a look at it
Try like this:
const [filter, setFilter] = useState("");
const menProducts = useSelector((state) => state.menProducts);
const searchText = (event) => {
setFilter(event.target.value);
};
useEffect(() => {
const dataSearch = (filter) =>
menProducts.filter((id) => {
// function
});
dataSearch(filter);
}, [filter]);
return (
<main>
{dataSearch.map((menproduct) => (
<ProductMen key={menproduct}/> <---imported <ProductMen/> component is a component that use useDispatch for store reducer and it also displayed products.
))}
</main>
)
To use the useEffect hook you have to pass a function and a dependencies array.
In this case I've used an anonymous function and inside of that I've defined the function dataSearch and on the dependencies array I've just included the filter so each time the filter value changes the useEffect gets executed.

State of child when parent calls useState update function

Suppose I have the following code snippet (Please consider it as a pseudo code)
Parent.js
const [state,action]=useState(0);
return <View><Child1/><Button onPress={()=>action(1)}/></View>
Child1.js
const [state]=useState(Math.random());
return <Text>{state}</Text>
So my question is when I click the button in the parent will the Chil1 state change or not.
On my local machine it seems it changes.
The benefit of useState is that once a component is mounted, the state value does not change across re-renders until the update state function is called, passing a new state value.
Therefore, even though your parent component Button press state change triggers a rerender of the child, since the child component is only being rerendered and not unmounted/remounted, the initial state of Math.random() would remain the same.
See useState in React: A complete guide
I don't know what exact scenario is, but if you just set default state, the state will be memorized like Scenario 1
Scenario 1
In this way, the state of Child will not be changed even if Parent re-render
const Child = () => {
const [state] = useState(Math.random());
return <div>{state}</div>
}
const Parent = () => {
const [, action] = useState(true);
return (
<>
<button onClick={() => action(false)}>Not Change</button>
<Child />
</>
);
}
Scenario 2
Unless you remove it and then re-render Parent even if memorize all Child, that is
const Child = () => {
const [state] = useState(Math.random());
return <div>{state}</div>
}
const Parent = () => {
const [state, action] = useState(true);
useEffect(() => {
if (!state) action(true)
}, [state])
return (
<>
<button onClick={() => action(false)}>Change</button>
{state && <Child />}
</>
);
}
Scenario 3
By the may, if you don't use default state, in this way, it will be changed every rendering like that
const Child = () => {
return <div>{Math.random()}</div>
}
const Parent = () => {
const [, action] = useState(true);
return (
<>
<button onClick={() => action(prev => !prev)}>Change</button>
<Child />
</>
);
}
Scenario 4
If we don't want Child to re-render, we can try memo to memorize it
const Child = memo(() => {
return <div>{Math.random()}</div>
})
Scenario 5
However, when Child has props, perhaps we should invole useCallback or useMemo to make sure the values or memory addresses of props are "fixed" like constant, so that Child won't re-render
(We don't have to use useCallback or useMemo all the time, it doesn't much matter when there is no performance problem)
const Child = memo((props) => {
return <div {...props}>{Math.random()}</div>
})
const Parent = () => {
const [, action] = useState(true);
const style = useMemo(() => ({}), [])
const onOK = useCallback(() => alert(1), [])
return (
<>
<button onClick={() => action(prev => !prev)}>Change</button>
<Child className="test" style={style} onClick={onOK} />
</>
);
}

Why is my state not updating in another state?

Wy state is not updating in another state, I don't know how I can fix this problem.
I wanna do a multiplayer card game with Socket.io, but I run into this problem. Whenever the selectColor state is changing, it's not going to update in the other state. I tried to print the state whenever I click on the component, but the state is just equal as the initial state. Does anybody know how to solve this?
Thanks
const [cards, setCards] = useState([]);
const [id, setId] = useState();
const [color, setColor] = useState("?");
const [selectColor, setSelectColor] = useState(false);
useEffect(() => {
socket.on("id", (id) => setId(id));
socket.on("forceDisconnect", () => setId("Full"));
socket.on("cards", (data) => {
setKarten([
data.map((element) => {
return (
<div
onClick={() =>
console.log(selectColor)
}
key={element}
>
<Card card={element} />
</div>
);
}),
]);
});
socket.on("selectColor", () => {
setSelectColor(true);
console.log("selectColor");
});
}, []);
You have created a closure and placed the value of selectColor in it when your socket.on("cards", etc. ) callback was executed (which is therefore 'frozen in time').
It is no good to create your react elements when your data arrives and store them away in your state. You are supposed to create them when your render function is called. Something like so:
socket.on("cards", (data) => setCards(data));
return (
<>
{ cards.map(
card => (
<div onClick={() => console.log(selectColor)} key={card}>
<Card card={card} />
</div>
)
)}
</>
);

changes to state issued from custom hook not causing re-render even though added to useEffect

I have a custom hook that keeps a list of toggle states and while I'm seeing the internal state aligning with my expectations, I'm wondering why a component that listens to changes on the state kept by this hook isn't re-rendering on change. The code is as follows
const useToggle = () => {
const reducer = (state, action) => ({...state, ...action});
const [toggled, dispatch] = useReducer(reducer, {});
const setToggle = i => {
let newVal;
if (toggled[i] == null) {
newVal = true;
} else {
newVal = !toggled[i];
}
dispatch({...toggled, [i]: newVal});
console.log('updated toggled state ...', toggled);
};
return {toggled, setToggle};
};
const Boxes = () => {
const {setToggle} = useToggle()
return Array.from({length: 8}, el => null).map((el,i) =>
<input type="checkbox" onClick={() => setToggle(i)}/>)
}
function App() {
const {toggled} = useToggle()
const memoized = useMemo(() => toggled, [toggled])
useEffect(() => {
console.log('toggled state is >>>', toggled) // am not seeing this on console after changes to toggled
}, [toggled])
return (
<div className="App">
<Boxes />
</div>
);
}
It's because you are using useToggle twice.
once in the App
another one in the Boxes.
When you dispatch the action in Boxes, it's updating the toggled instance for Boxes (which is not retrieved in it).
Think of your custom hook like how you use useState. When you use useState, each component gets its own state. Same goes for the custom hook.
So there are a few ways you can address the issue.
Pass the setToggle from App to Boxes via prop-drilling
Use Context API (or Redux or other statement management library to pass
setToggle instance in the App component down)
Here is an example of prop-drilling.
You can follow along
const Boxes = ({ setToggle }) => {
// const { setToggle } = useToggle();
return Array.from({ length: 8 }, el => null).map((el, i) => (
<input key={i} type="checkbox" onClick={() => setToggle(i)} />
));
};
function App() {
const { toggled, setToggle } = useToggle();
useEffect(() => {
console.log("toggled state is >>>", toggled); // am not seeing this on console after changes to toggled
}, [toggled]);
return (
<div className="App">
<Boxes setToggle={setToggle} />
</div>
);
}
Note: that I added key props in Boxes using the index i(and it is a bad practice by the way)
You can see that it's now working as you'd expect.

Resources