How do I rerender a component from another competent - reactjs

I have an ActivityFeed of posts. When I click on an icon in the ActivityPost component it saves the postId in a global state (EditPostIndex) that's meant to act like a toggle for the CreatePost and EditPost component in the Activity feed. When I click on the editpost icon it brings up the body of the post that I'm suppose to edit
ActivityFeed
const ActivityFeed = () => {
const {posts} = useContext(GlobalContext);
const {editPostIndex} = useContext(GlobalContext);
return (
<div id="mobile-activity">
<DeviceNav />
{ editPostIndex === null ?
<CreatePost />
:
<EditPost />
}
{posts.slice(0).reverse().map(post => (
<ActivityPost key={post.id} post={post} />
))}
</div>
)
}
ActivityPost
function ActivityPost({post, index}) => {
const {toggleEditPost} = useContext(GlobalContext);
function updatePost(index){
toggleEditPost(index)
}
}
EditPost.js
const EditPost = () => {
const {posts} = useContext(GlobalContext);
const {updatePost} = useContext(GlobalContext);
const {editPostIndex} = useContext(GlobalContext);
let val = posts[editPostIndex].body;
let [body, setBody] = useState(val);
function editPost() {
//update
}
return (
<div id="make-post">
<div id="create-post">
<textarea value={body} onChange={(e) => setBody(e.target.value)} id="post-activity" placeholder="Tell them what you think."></textarea>
</div>
<div id="create-post-actions">
<button onClick={editPost} id="post">Edit</button>
</div>
</div>
)
}
GlobalState/GlobalContext
const initialState = {
posts: posts,
editPostIndex: null
}
export const GlobalProvider = ({children}) => {
const [state, dispatch] = useReducer(AppReducer, initialState)
function toggleEditPost(index = null){
dispatch({
type: 'TOGGLE_EDIT_POST',
payload: index
})
//alert(index);
}
function updatePost(post){
dispatch({
type: 'UPDATE_POST',
payload: post
})
toggleEditPost(null);
}
}
The problem is that in EditPost component let val = posts[editPostIndex].body; let [body, setBody] = useState(val); the useState only renders once because the EditPostIndex is already changed. How do I make it so when I click on the edit post icon the let [body, setBody] = useState(val); changes to the posts body that I want to edit? Or rerender the EditPost component so setBody is set again?

In this case, I'd say you'd need more hooks like useState & useEffect to detect a change in your context EditPost.js.
const [postIndex, setPostIndex] = useState(editPostIndex);
useEffect(() => {
if(editPostIndex !== postIndex){
setPostIndex(editPostIndex);
setBody(posts[editPostIndex].body)
}
}, [setPostIndex, postIndex])

You can use redux, which is actually built for this purpose. With redux you can subscribe components to the redux store and then push updates which will automatically update the subscribed components.

Related

React useState async setter doesn't update value passed as props

I have this component in my React project -
const ViewPost = (props: Props) => {
const [listingData, setListingData] = useState<any>({})
const [auctionData, setAuctionData] = useState<any>({})
useEffect(() => {
if (props.listingId) {
getListingData()
}
}, [props.listingId])
const getListingData = async () => {
const { data } = await getListingById(props.listingId)
setListingData(data?.data)
if (data.data.isTimedAuction) {
auctions(data.data.auctionId)
}
}
const auctions = async (auctionId: any) => {
const auction = await getAuctions(auctionId)
console.log('auction', auction.data)
setAuctionData(auction.data)
}
return (
<>
<Navbar />
<div className={classes.viewPostPage}>
<div className={classes.bodyContainer}>
<Details
data={listingData as any}
updateListing={getListingData}
auctionData={auctionData}
/>
</div>
</div>
</>
)
}
export default ViewPost
Basically, I'm getting data from an API and assigning it to auctionData.
console.log(auction.data) shows me the desired result but when I pass auctionData as props into Details I get an empty object which leads to a lot of issues, since useState is async.
How can I overcome this problem?
const [auctionData, setAuctionData] = useState<any>({})
your default value is an empty object, that causes the problems.
should set null or undefined as default value, and hide the Details when not have the data.
Use loading state. Once data is fully fetched from api then pass to child component. I think what is happeing here is that child component is called with empty state variable while data is still being fetched.
const [isLoading, setIsLoading] = useState(true)
const getListingData = async () => {
const { data } = await getListingById(props.listingId)
.then((data) => {setListingData(data)})
.then((data) => {
setTimeout(() => {
setIsLoading(false)
}, 1000)
})
if (data.data.isTimedAuction) {
auctions(data.data.auctionId)
}
}
and then return
if (isLoading) {
return (
<div>
Loading...
</div>
)
}
return (
<>
<Navbar />
<div className={classes.viewPostPage}>
<div className={classes.bodyContainer}>
<Details
data={listingData as any}
updateListing={getListingData}
auctionData={auctionData}
/>
</div>
</div>
</>
)
}

Fetch data from the backend, then pass the data to editorState using draft-js

I'm creating a component that will display the data from backend by using draft-js. The data from the backend also is being stored using draft-js. The data is not being display and also there's no error message.
Sample Data from the backend is being parse before passing to the viewContent.js
Hello World
I tried to create a variable to check if the code is working. I tried this approach const sample = <p>Hello World. This one is working if pass this on contenBlocks.
viewContent.js
import {
EditorState,
ContentState,
convertFromHTML,
} from 'draft-js';
import { Editor } from 'react-draft-wysiwyg';
import draftToHtml from 'draftjs-to-html';
const viewContent = ({ content }) => {
const [editorState, setEditorState] = useState();
const clearState = () => {
ContentState.createFromText('');
};
const handleEditorChange = (state) => {
setEditorState(state);
let currentContentAsHTML = JSON.stringify(
draftToHtml(convertToRaw(editorState.getCurrentContent()))
);
convertedContent(currentContentAsHTML);
};
useEffect(() => {
const contentBlocks = convertFromHTML(content);
const contentState = ContentState.createFromBlockArray(
contentBlocks.contentBlocks,
contentBlocks.entityMap
);
setEditorState(EditorState.createWithContent(contentState));
}, [content]);
return (
<div className='comment-container p-2 border rounded-md'>
<Editor
editorState={editorState}
onEditorStateChange={handleEditorChange}
wrapperClassName='wrapper-class'
editorClassName='editor-class'
toolbarClassName='toolbar-class'
onClick={clearState}
/>
</div>
);
};
export default viewContent;
Parent Compontent
<viewContent
content={taskInfo.taskNote}
convertedContent={(convertedContent) =>
setTaskInfo((prevState) => ({
...prevState,
taskNote: convertedContent,
}))
}
/>
Error
You should set editor state after ViewContent component rendered completely. update your component as below:
...
useEffect(() => {
const contentBlocks = convertFromHTML(content);
const contentState = ContentState.createFromBlockArray(
contentBlocks.contentBlocks,
contentBlocks.entityMap
);
setEditorState(EditorState.createWithContent(contentState));
}, [content]);
...

useEffect dosn't save data in localstorage

I have a simple app, sorta for chat purpuses. I fetch data from static file in json format. So this app shows all the messages from that file but also I want to edit the messeges, delete them and add via local storage. For that I used useEffect, but after refresh all the changes I do disappear.
This is my component:
export const WorkChat = (props) => {
const [messageValue, setMessageValue] = useState('');
const [edit, setEdit] = useState(null);
const [editmessageValue, setMessageEditValue] = useState('')
const submitMessage = () => {
const newMessage = {
id: Math.floor(Math.random() * 10000),
message: messageValue
}
props.addMessage(newMessage);
setMessageValue('')
}
const removeMsg = (id) => {
props.deleteMessage(id)
}
const goToEditMode = (message) => {
setEdit(message.id);
setMessageEditValue(message.message)
}
const saveChanges = (id) => {
const newMessagesArray = props.messages.map(m => {
if(m.id === id){
m.message = editmessageValue
}
return m
})
props.updateMessage(newMessagesArray);
setEdit(null)
}
useEffect(()=> {
let data = localStorage.getItem('work-messages');
if(data){
props.setMessages(JSON.parse(data))
}
}, []);
useEffect(()=> {
localStorage.setItem('work-messages', JSON.stringify(props.messages))
},[props.messages])
return (
<div className={s.workChatContainer}>
<input className={s.workInput} placeholder='Enter work message...' onChange={(e)=> setMessageValue(e.target.value)} value={messageValue}/>
<button className={`${s.btn} ${s.sendBtn}`} onClick={()=>submitMessage()}><SendIcon style={{fontSize: 20}}/></button>
<div>
{props.messages.map(m => (
<div key={m.id} className={s.messages}>
{edit !== m.id ? <div>
<span className={s.message}>{m.message}</span>
<button className={`${s.btn} ${s.deleteBtn}`} onClick={()=> removeMsg(m.id)}><DeleteOutlineIcon style={{fontSize: 15}}/></button>
<button className={`${s.btn} ${s.editBtn}`} onClick={()=> goToEditMode(m)}><EditIcon style={{fontSize: 15}}/></button>
</div>
:
<form>
<input className={s.editInput} value={editmessageValue} onChange={(e)=> setMessageEditValue(e.target.value)}/>
<button className={`${s.btn} ${s.saveBtn}`} onClick={()=> saveChanges(m.id)}><BeenhereIcon style={{fontSize: 15}}/></button>
</form>
}
</div>
))}
</div>
</div>
)
}
Just in case, this is my container component:
import { connect } from "react-redux"
import { setFloodMessagesAC, addFloodMessageAC, deleteFloodMessageAC, upadateMessageAC } from "../../redux/flood-reducer"
import { FloodChat } from "./FloodChat"
import { useEffect } from 'react'
import data from '../../StaticState/dataForFlood.json'
const FloodChatApiContainer = (props) => {
useEffect(()=> {
props.setFloodMessages(data)
}, [])
return <FloodChat messages={props.messages}
setFloodMessages={props.setFloodMessages}
addFloodMessage={props.addFloodMessage}
deleteFloodMessage={props.deleteFloodMessage}
upadateMessage={props.upadateMessage}
/>
}
const mapStateToProps = (state) => ({
messages: state.flood.messages
})
export const FloodChatContainer = connect(mapStateToProps, {
setFloodMessages: setFloodMessagesAC,
addFloodMessage: addFloodMessageAC,
deleteFloodMessage: deleteFloodMessageAC,
upadateMessage: upadateMessageAC
})(FloodChatApiContainer)
Why useEffect doesn't work? It seems to me like it should, but it doesnt.
I figured it out. Since I use data from static file, I need to implement functions that get/set data from/to local storage right where I import it which is container component. Once I put those useEffect functions in container component it works perfectly well.
const FloodChatApiContainer = (props) => {
useEffect(()=> {
props.setFloodMessages(data)
}, [])
useEffect(()=> {
let data = JSON.parse(localStorage.getItem('flood-messages'));
if(data){
props.setFloodMessages(data)
}
console.log('get')
}, [])
useEffect(() => {
localStorage.setItem('flood-messages', JSON.stringify(props.messages));
console.log('set')
}, [props.messages]);
return <FloodChat messages={props.messages}
setFloodMessages={props.setFloodMessages}
addFloodMessage={props.addFloodMessage}
deleteFloodMessage={props.deleteFloodMessage}
upadateMessage={props.upadateMessage}
/>
}
const mapStateToProps = (state) => ({
messages: state.flood.messages
})
export const FloodChatContainer = connect(mapStateToProps, {
setFloodMessages: setFloodMessagesAC,
addFloodMessage: addFloodMessageAC,
deleteFloodMessage: deleteFloodMessageAC,
upadateMessage: upadateMessageAC
})(FloodChatApiContainer)

How to get the React context from another context?

I have two contexts - gameContext, roundContext. I am using useReducer to manipulate the state. Can I dispatch action in gameContext from roundContext reducer (RoundReducer)?
The reducer function is defined outside RoundProvider component.
const RoundReducer = (state: RoundStateType, action: any) => {
///sth
}
const RoundProvider: React.FC<{}> = ({ children }) => {
const [state, dispatch] = useReducer(RoundReducer, initState);
return (
<RoundContext.Provider
value={{ roundState: state, roundDispatch: dispatch }}>
{children}
</RoundContext.Provider>
);
};
The GameProvider component looks the same.
If you have nested contexts GameContext and RoundContext you can access the outer game context from the inner round, then call a setter/dispatch method to initiate a change in each. The inner RoundContext provider is inside the GameContext provider, so there (dispatch in this example) you have access to the methods exposed by the GameContext.
const GameContext = React.createContext(null);
const GameProvider = ({ children }) => {
const [gameState, setGameState] = React.useState();
return (
<GameContext.Provider value={{ gameState, setGameState }}>
{children}
</GameContext.Provider>
);
};
const useGame = () => React.useContext(GameContext)
const RoundContext = React.createContext(null);
const RoundProvider = () => {
const { gameState, setGameState } = useGame();
const [roundState, setRoundState] = React.useState();
const dispatch = (value) => {
// Do something to both the round and the game state
setGameState(value.toUpperCase());
setRoundState(value);
};
return (
<RoundContext.Provider value={{ roundState, dispatch }}>
{children}
</RoundContext.Provider>
);
}
const useRound = () => React.useContext(RoundContext)
const Main = () => {
const game = useGame()
const round = useRound()
const handleAction = () => {
round.dispatch('some value that also goes to the game')
}
return <>
<input type='text' onChange={handleAction} />
<div>{game.gameState}</div>
<div>{round.roundState}</div>
</>
}
const App = () => (<GameProvider>
<RoundProvider>
<Main />
</RoundProvider>
</GameProvider>)
Here's a codesandbox example:
https://codesandbox.io/s/xenodochial-wind-gkhje
pass the prop from the game context to roundContext or other way around, let say if you're using react hooks - useState,in props pass the setValue.
Please review the following code.
also, this is just for referance
export default function GameComponent(){
//suppose this is parent component and you want to change the value from child componenet
const [value, setValue] = useState("Patel");
return(
<div>
<h1>{value}</h1>
<RoundComponent setValue={setValue} value={value} />
//pass props like this
</div>
)
}
now coming back to round component
export default function RoundComponent(props){
return(
<div>
<input type="text" name="name" value={props.value} onChange={e=>props.setValue(e.target.value)}/>
</div>
)
}
I hope this answers your question.

I am having with set state in my Shopify App using Node React

I am making a Shopify app using Shopify Polaris.
I used the ActionList component.
https://polaris.shopify.com/components/actions/action-list
I want to change the state value on the onAction event.
I did like this.
const [searchValue, setSearchValue] = useState('');
const handleAction = (value) => {
setSearchValue(value);
}
const a = ["A","B"];
const searchResultsMarkup = (
<ActionList
items={[
{
content: a[0],
onAction: handleAction(a[0]),
},
{
content: a[1],
onAction: handleAction(a[1]),
},
/>
);
I am a beginner in React.
So maybe a silly question.
but kindly teach me.
Thanks
You are passing it down as a prop, so you will have to change it within the Component.
import React, { useState } from 'react';
export const Parent = () => {
const [searchValue, setSearchValue] = useState('');
const handleAction = value => {
setSearchValue(value);
}
return <ActionList changeSearchVal={handleAction} />
}
export const ActionList = ({ changeSearchVal }) => {
return (
<form>
<input type="text" onChange={e => changeSearchVal(e.target.value)}/>
</form>
)
}
If you want to change the searchValue within ActionList.

Resources