I am not able to load default value style in React-draft-wysiwyg .
Codesandbox Link: Editor
what I tried ?
I am using react-draft-wysiwyg library for editor and draft-js for initializing and converting , and I have passed default value with style. if i remove style tag it works fine. but after adding style it doesn't work. how to fix style issue in default value
import { Editor } from "react-draft-wysiwyg";
import { EditorState, ContentState, convertFromHTML } from "draft-js";
import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
export default function App() {
const defaultValueRender = !true;
const defaultValue = "**<p style="color:red**">This is a paragraph.</p>";
const initialState = () => EditorState.createEmpty();
const [editorState, setEditorState] = useState(initialState);
useEffect(() => {
if (defaultValue !== "") {
setEditorState(
EditorState.createWithContent(
ContentState.createFromBlockArray(convertFromHTML(defaultValue))
)
);
}
}, []);
const onChange = async (value) => {
await setEditorState(value);
};
return (
<div className="App">
<Editor
editorState={editorState}
toolbarClassName="toolbarClassName"
wrapperClassName="wrapperClassName"
editorClassName="editorClassName"
onEditorStateChange={(value) => onChange(value)}
stripPastedStyles
ariaLabel="draftEditor"
/>
</div>
);
}
thanks to gokhan answer, i finally solved to get initial values to react-draft-wysiwyg + react-hook-form
import React, { useState, useEffect, useCallback } from 'react'
import { ContentState, EditorState, convertToRaw } from 'draft-js'
import { Editor } from 'react-draft-wysiwyg'
import htmlToDraft from 'html-to-draftjs'
import draftToHtml from 'draftjs-to-html'
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'
import { styContainer, styWrapper, styToolbar, styEditor } from './style'
const getInitialState = (defaultValue) => {
if (defaultValue) {
const blocksFromHtml = htmlToDraft(defaultValue)
const { contentBlocks, entityMap } = blocksFromHtml
const contentState = ContentState.createFromBlockArray(
contentBlocks,
entityMap
)
return EditorState.createWithContent(contentState)
} else {
return EditorState.createEmpty()
}
}
const RichEditor = ({ defaultValue, onChange }) => {
const [editorState, setEditorState] = useState()
const [defaultValueState, setdefaultValueState] = useState()
useEffect(() => {
if (defaultValue) {
const initialState = getInitialState(defaultValue)
onEditorDefaultStateChange(initialState)
}
}, [onEditorDefaultStateChange, defaultValue])
const onEditorDefaultStateChange = useCallback(
(editorState) => {
setdefaultValueState(editorState)
return onChange(
draftToHtml(convertToRaw(editorState.getCurrentContent()))
)
},
[onChange]
)
const onEditorStateChange = useCallback(
(editorState) => {
setEditorState(editorState)
return onChange(
draftToHtml(convertToRaw(editorState.getCurrentContent()))
)
},
[onChange]
)
return (
<div className={styContainer}>
<Editor
editorState={editorState ? editorState : defaultValueState}
onEditorStateChange={onEditorStateChange}
wrapperClassName={styWrapper}
toolbarClassName={styToolbar}
editorClassName={styEditor}
/>
</div>
)
}
RichEditor.propTypes = {}
RichEditor.defaultProps = {}
export default RichEditor
You could use html-to-draftjs for converting html strings with inline styles.
import React, { useState } from 'react';
import PropTypes from 'prop-types';
// DRAFT
import { EditorState, ContentState, convertToRaw } from 'draft-js';
import { Editor } from 'react-draft-wysiwyg';
import draftToHtml from 'draftjs-to-html';
import htmlToDraft from 'html-to-draftjs';
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
// PURIFY
import DOMPurify from 'dompurify';
// INITIAL STATE
// EditorState.createWithContent(ContentState.createFromBlockArray(convertFromHTML(defaultValue)))
const getInitialState = (defaultValue) => {
if (defaultValue) {
const blocksFromHtml = htmlToDraft(defaultValue);
const { contentBlocks, entityMap } = blocksFromHtml;
const contentState = ContentState.createFromBlockArray(contentBlocks, entityMap);
return EditorState.createWithContent(contentState);
} else {
return EditorState.createEmpty();
}
};
const DraftEditor = ({ defaultValue, onChange }) => {
const [editorState, setEditorState] = useState(getInitialState(defaultValue));
const onEditorChange = (val) => {
setEditorState(val);
const rawContentState = convertToRaw(val.getCurrentContent());
const htmlOutput = draftToHtml(rawContentState);
const cleanHtml = DOMPurify.sanitize(htmlOutput);
onChange && onChange(cleanHtml);
};
return (
<Editor
editorState={editorState}
onEditorStateChange={onEditorChange} />
);
};
DraftEditor.propTypes = {
defaultValue: PropTypes.string,
onChange: PropTypes.func.isRequired,
};
export default DraftEditor;
Related
So I am trying to store a global state using context to allow me to use the same state across different components.
The issue I am having is that when I set the global state in 1 component and try to access it in the other component to use the state. It appears to be null and I cannot figure out why?
The first component where I set the global state in will always be rendered before the component shown that seems to have an empty value for the global state.
GlobalStateProvider component:
import React from "react";
import { useState, useEffect } from "react";
import axios from "axios";
const defaultActivitiesState = [];
const globalStateContext = React.createContext(defaultActivitiesState);
const dispatchStateContext = React.createContext([]);
export const useGlobalState = () =>
[
React.useContext(globalStateContext),
React.useContext(dispatchStateContext)
];
const GlobalStateProvider = ({ children }) => {
const [state, dispatch] = React.useReducer((state, newValue) => (state, newValue),
defaultActivitiesState
);
return (
<globalStateContext.Provider value={state}>
<dispatchStateContext.Provider value={dispatch}>
{children}
</dispatchStateContext.Provider>
</globalStateContext.Provider>
);
}
export default GlobalStateProvider;
Component I set the global state in:
import react from "react";
import { useState, useEffect, useMemo } from "react";
import { MapContainer, TileLayer, Popup, Polyline } from "react-leaflet";
import axios from "axios";
import polyline from "#mapbox/polyline";
import MapComp from "./MapComp";
import { useGlobalState } from "./GlobalStateProvider";
function Map() {
// ------- global state
const [activities, setActivities] = useGlobalState(); // global state
//const [activities, setActivities] = useState([]);
//const [polylines, setPolylines] = useState(null); // as empty array value is still truthy
const [isLoading, setIsLoading] = useState(true);
const [mapMode, setMapMode] = useState("light");
const [mapStyle, setMapStyle] = useState(
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
);
const [showMarkers, setShowMarkers] = useState(true);
useEffect(() => {
setActivitieData();
console.log("activities after useEffect", activities)
}, []);
const polylines = useMemo(() => {
console.log("activities inside memo", activities)
console.log("activities.len =", activities.length);
if (activities.length) {
console.log("past len");
const polylineArray = [];
for (const item of activities) {
const polylineData = item.map.summary_polyline;
const activityName = item.name;
const activityType = item.type;
polylineArray.push({
positions: polyline.decode(polylineData),
name: activityName,
activityType: activityType,
});
}
setIsLoading(false);
return polylineArray;
}
return null;
}, [activities]);
const toggleMarkers = () => {
setShowMarkers((show) => !show);
};
const getActivityData = async () => {
console.log("calling")
const response = await axios.get(
"http://localhost:8800/api/"
);
return response.data;
};
const setActivitieData = async () => {
const activityData = await getActivityData();
setActivities(activityData);
console.log("Global activities state = ", activities);
};
return !isLoading && polylines ? (
<>
<div className="select-container">
<button className="toggle-markers" onClick={() => toggleMarkers()}>
Toggle Markers
</button>
</div>
<MapComp
className={`${mapMode}`}
activityData={{ polylines }}
showMarkers={showMarkers}
/>
</>
) : (
<div>
<p>Loading...</p>
</div>
);
}
export default Map;
component that has an empty value for global state:
import React from 'react';
import { useGlobalState } from './GlobalStateProvider';
function ActivityList() {
const [activities, setActivities] = useGlobalState();
let displayValues;
displayValues =
activities.map((activity) => {
return (
<div>
<p>{activity.name}</p>
<p>{activity.distance}m</p>
</div>
);
})
return (
<>
<p>Values</p>
{displayValues}
</>
);
}
export default ActivityList;
App.js:
function App() {
return (
<GlobalStateProvider>
<div className="App">
<NavBar />
<AllRoutes />
</div>
</GlobalStateProvider>
);
}
export default App;
I'm developing a pokedex using pokeAPI through react, but I'm developing a feature where I can favorite pokemons and with that through a context, I can store the names of these pokemons in a global array. I've already managed to test and verify that the pokemon names are going to this "database" array inside the pokeDatabase const in my context, but my goal now is to pass this array to localstorage so that the browser recognizes these favorite pokemons instead of disappearing every time I refresh the page, my solution was to try to create a useEffect inside the context so that every time I refresh my application, this information is saved in localStorage, but without success. What better way to achieve this?
context:
import { createContext } from "react";
const CatchContext = createContext({
pokemons: null,
});
export default CatchContext;
provider
import React, { useEffect } from "react";
import CatchContext from "./Context";
const pokeDatabase = {
database: [],
};
const CatchProvider = ({ children }) => {
useEffect(() => {
const dataStorage = async () => {
await localStorage.setItem('pokemons', JSON.stringify(pokeDatabase.database));
}
dataStorage();
}, [])
return (
<CatchContext.Provider value={{ pokemons: pokeDatabase }}>
{children}
</CatchContext.Provider>
);
}
export default CatchProvider;
pageAddPokemon
import * as C from './styles';
import { useContext, useEffect, useState } from 'react';
import { useApi } from '../../hooks/useApi';
import { useNavigate, useParams } from 'react-router-dom';
import PokeInfo from '../../components/PokeInfo';
import AddCircleOutlineIcon from '#mui/icons-material/AddCircleOutline';
import DoNotDisturbOnIcon from '#mui/icons-material/DoNotDisturbOn';
import CatchContext from '../../context/Context';
const SinglePokemon = () => {
const api = useApi();
const { pokemons } = useContext(CatchContext);
const { name } = useParams();
const navigate = useNavigate();
const handleHompage = () => {
navigate('/');
}
const [loading, setLoading] = useState(false);
const [imgDatabase, setImgDatabase] = useState('');
const [infoPokemon, setInfoPokemon] = useState([]);
const [pokemonTypes, setPokemonTypes] = useState([]);
const [isCatch, setIsCatch] = useState(false);
useEffect(() => {
const singlePokemon = async () => {
const pokemon = await api.getPokemon(name);
setLoading(true);
setImgDatabase(pokemon.sprites);
setInfoPokemon(pokemon);
setPokemonTypes(pokemon.types);
setLoading(false);
console.log(pokemons.database);
}
singlePokemon();
verifyPokemonInDatabase();
}, []);
const verifyPokemonInDatabase = () => {
if (pokemons.database[infoPokemon.name]) {
return setIsCatch(true);
} else {
return setIsCatch(false);
}
}
const handleCatchAdd = async () => {
if (isCatch === false) {
if (!pokemons.database[infoPokemon.name]);
pokemons.database.push(infoPokemon.name);
setIsCatch(true);
}
}
const handleCatchRemove = async () => {
if (isCatch === true) {
if (!pokemons.database[infoPokemon.name]);
pokemons.database.splice(pokemons.database.indexOf(toString(infoPokemon.name)), 1);
setIsCatch(false);
}
}
return (
<C.Container>
<PokeInfo
name={infoPokemon.name}
/>
<C.Card>
<C.Info>
<C.Imgs>
<img src={imgDatabase.front_default} alt="" />
<img src={imgDatabase.back_default} alt="" />
</C.Imgs>
<h2 id='types'>Tipos</h2>
{pokemonTypes.map(type => {
return (
<C.Types>
<h2>{type.type.name}</h2>
</C.Types>
)
})}
{isCatch ? (
<DoNotDisturbOnIcon id='iconCatched' onClick={handleCatchRemove}/>
): <AddCircleOutlineIcon id='icon' onClick={handleCatchAdd}/>}
</C.Info>
</C.Card>
<C.Return>
<button onClick={handleHompage}>Retornar a Pokédex</button>
</C.Return>
</C.Container>
)
}
export default SinglePokemon;
I am receiving an error of "HomePage.jsx:16 Uncaught TypeError: elements.map is not a function" when trying to change the boxtitle for the array component. I have tried moving around functions and such but nothing seems to work.
I basically only want to change the title for the certain array object with the same boxid. Below is my code.
HomePage.jsx:
import react from 'react';
import { useEffect, useContext } from 'react';
import '../App.css';
import Todobox from './Todobox';
import { ElementContext } from '../ElementContext';
export default function HomePage(){
const { elements, setElements, newElement, elementId } = useContext(ElementContext);
return(
<div className='page-container'>
<div className='header'>
<a className='header-title'>Trello Clone!</a>
<a className='header-button' onClick={newElement}>Create a list</a>
</div>
<div className='element-field'>
{elements.map((element) => <Todobox key={element.boxid} boxid={element.boxid} boxtitle={element.boxtitle}/>)}
</div>
</div>
)
}
Todobox.jsx:
import React from 'react';
import Item from './Item';
import { useState, useContext } from 'react';
import '../App.css';
import { ElementContext } from '../ElementContext';
export default function Todobox({ boxtitle, boxid }){
const { elements, setElements } = useContext(ElementContext);
const [boxheader, setBoxHeader] = useState();
const handleSubmit = (e) => {
const object = elements.find(obj => {
if (obj.boxid === boxid){
setBoxHeader(e.target.value)
return obj
}})
setElements({...object, boxtitle: boxheader})
}
const handleKeydown = (e) => {
if(e.keyCode == 13 && e.shiftKey == false){
e.preventDefault();
handleSubmit(e)
}
}
return(
<div className='element-box'>
<textarea className='element-title-input' placeholder={boxtitle} onKeyDown={handleKeydown}/>
<Item />
<textarea
className='element-input'
type='text'
placeholder={`Add item... ${boxid}`}
onClick={() => {console.log(boxid)}}
/>
</div>
)
}
ElementContext.js:
import React, { createContext, useState } from 'react';
import Todobox from './components/Todobox';
export const ElementContext = createContext();
export const ElementContextProvider = ({children}) => {
const [elements, setElements] = useState([]);
const [elementId, setElementId] = useState(1);
const [title, setTitle] = useState('Add title...');
const [refDict, setRefDict] = useState({});
const newElementId = (elements) =>{
setElementId(elementId + 1);
console.log(elementId)
}
const newElement = () => {
newElementId();
if (!refDict[elementId]) {
setElements(prev => [...prev, { boxtitle: title, boxid: elementId }]);
setRefDict((prev) => ({...prev, [elementId]: true}));
}
console.log(elements);
};
const value = {
elements,
setElements,
newElement,
elementId,
};
return(
<ElementContext.Provider value={value}>
{children}
</ElementContext.Provider>
)
};
Code Sandbox
Any help is appreciated since I am new and still learning! :)
Few points to handle
const newElement = () => {
newElementId(); // this wont update as react batches the state updates
// try using userRef for elementId
// create new element id here, and then set it
// const newId = elementId + 1
// setElementId(newElementId)
if (!refDict[elementId]) {
setElements(prev => [...prev, { boxtitle: title, boxid: elementId }]);
setRefDict((prev) => ({...prev, [elementId]: true}));
}
console.log(elements);
};
on submit
const object = elements.find(obj => {
if (obj.boxid === boxid){
setBoxHeader(e.target.value) // wont update the as React will likely batch the state updates
return obj
}})
setElements({...object, boxtitle: boxheader}) // is this an array ?
instead try
const object = elements?.find(obj => obj.boxid === boxid)
if (object) {
setBoxHeader(e.target.value)
setElements([ object, { boxtitle: e.target.value, boxId: elementId } ]) // what is the structure of array elements
}
you will find the new React documentation about updating arrays useful
I have the main page 'feed' where i used to have three functions, but I moved it into custom context. The console logs in context file output all the objects correctly, but nothing is visible in feed when i concole.log them.
context file:
import React, { useEffect, createContext, useContext, useState } from 'react'
import { useMemo } from 'react';
import getPosts from '../api/getPosts';
import filterImportedPosts from '../utils/filterImportedPosts';
export const ItemContext = createContext({
postData: {}, setPostData: () => { }
});
export const FilteredItemsContext = createContext({ filteredItems: [], setFilteredItems: () => { } })
export const FilterContext = createContext({ filter: '', setFilter: () => { } })
export function useItemContext() {
return useContext(ItemContext)
}
export function useFilteredItemsContext() {
return useContext(FilteredItemsContext)
}
export function useFilterContext() {
return useContext(FilterContext)
}
export default function PostProvider({ children }) {
const [postData, setPostData] = useState([]);
const [filteredItems, setFilteredItems] = useState([]);
const [filter, setFilter] = useState('');
useEffect(() => {
getPosts(setPostData)
console.log('postData: ', postData)
}, []);
useEffect(() => {
// console.log(filter);
const tempFiltItems = filterImportedPosts(postData, filter);
setFilteredItems(tempFiltItems);
console.log('tempFiltItems: ', filteredItems)
}, [filter, postData]);
const filteredItemsState = useMemo(() => {
return { filteredItems, setFilteredItems }
}, [filteredItems, setFilteredItems])
return (
<FilterContext.Provider value={{ filter, setFilter }}>
<FilteredItemsContext.Provider value={filteredItemsState}>
<ItemContext.Provider value={{ postData, setPostData }}>
{children}
</ItemContext.Provider>
</FilteredItemsContext.Provider >
</FilterContext.Provider>
)
}
and here the feed file:
import React, { useState, useEffect } from 'react';
import SinglePost from '../components/singlePost/singlePost';
import FilterPane from '../components/filterPane/filterPane.feedPost';
import { Box, Spinner, Text } from '#chakra-ui/react';
import getPosts from '../api/getPosts';
import Loader from '../../common/Loader';
import filterImportedPosts from '../utils/filterImportedPosts';
import PostProvider, { useFilterContext, useFilteredItemsContext, useItemContext } from './../context/PostDataContext';
export default function Feed() {
//-----------------IMPORT DATA FROM SERVER----------------------
const [error, setError] = useState(null);
const { postData, setPostData } = useItemContext();
const { filter, setFilter } = useFilterContext();
const { filteredItems, setFilteredItems } = useFilteredItemsContext();
useEffect(() => {
console.log(filteredItems)
}, [filteredItems, setFilteredItems])
// this helps while the data is loaded
// if (postData.length === 0) {
// return (
// <Box pos='absolute' top='45vh' left='40%'>
// <Loader />
// </Box>
// )
// }
// console.log(filteredItems);
return (
<PostProvider>
<Box mt={'7vh'} mb={'7vh'} ml={'3vw'} mr={'3vw'} zIndex={200}>
<FilterPane
setFilter={setFilter}
filter={filter}
filteredItems={filteredItems}
/>
{error && (
<div>Error occurred while loading profile info. Details: {error}</div>
)}
{!error && (
<>
{filteredItems.map((item, index) => {
return <SinglePost key={index} item={item} />;
})}
</>
)}
</Box>
</PostProvider>
);
}
console window printscreen. As you can see the filteredItems in context exist but nothing gets shown in the actual feed - the objects are empty. Could someone assist please?
I am trying to add react-draft-wysiwyg editor. I am trying to get the current content of the editor but it's showing error as getCurrentContent() is not a function.
Can anyone help please?
import React, { useState } from 'react';
import { Editor } from "react-draft-wysiwyg";
import { EditorState } from "draft-js";
import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
import { convertToHTML } from 'draft-convert';
import DOMPurify from 'dompurify';
const NoteEditor = () => {
const [editorState, setEditorState] = useState(
() => EditorState.createEmpty(),
);
const [convertedContent, setConvertedContent] = useState("");
const handleEditorChange = (state) => {
setEditorState(state);
convertContentToHTML();
}
const convertContentToHTML = () => {
let currentContentAsHTML = convertToHTML(editorState.getCurrentContent());
setConvertedContent(currentContentAsHTML);
}
const createMarkup = (html) => {
return {
__html: DOMPurify.sanitize(html)
}
}
return(
<div className="note-editor">
<Editor
defaultEditorState={editorState}
onChange={handleEditorChange}
wrapperClassName="wrapper-class"
editorClassName="editor-class"
toolbarClassName="toolbar-class"
/>
<div className="preview" dangerouslySetInnerHTML={createMarkup(convertedContent)}></div>
</div>
)
};
export default NoteEditor;
you can use like this, sometime useState took time as its async,
const handleEditorChange = (state) => {
setEditorState(state);
convertContentToHTML(state);
}
const convertContentToHTML = (editorState) => {
let currentContentAsHTML = convertToHTML(editorState.getCurrentContent());
setConvertedContent(currentContentAsHTML);
}
or do useEffect with editorState,
useEffect(()=>{},[editorState])